﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml;
using DiscoveryLogic.Common.Utilities;

namespace DiscoveryLogic.Analytics.DataObjects.Networks
{
    /// <summary>
    /// 
    /// </summary>
    public class Link
    {
        #region props
        private Type _FromType;
        /// <summary></summary>
        public Type FromType
        {
            get
            {
                return this._FromType;
            }
            set
            {
                this._FromType = value;
            }
        }

        private Type _ToType;
        /// <summary></summary>
        public Type ToType
        {
            get
            {
                return this._ToType;
            }
            set
            {
                this._ToType = value;
            }
        }

        private string _FromKey;
        /// <summary></summary>
        public string FromKey
        {
            get
            {
                return this._FromKey;
            }
            set
            {
                this._FromKey = value;
            }
        }

        private string _ToKey;
        /// <summary></summary>
        public string ToKey
        {
            get
            {
                return this._ToKey;
            }
            set
            {
                this._ToKey = value;
            }
        }

        private double _Distance;
        /// <summary></summary>
        public double Distance
        {
            get
            {
                return this._Distance;
            }
            set
            {
                this._Distance = value;
            }
        }

        private LinkType _RelationType;
        /// <summary></summary>
        public LinkType RelationType
        {
            get
            {
                return this._RelationType;
            }
            set
            {
                this._RelationType = value;
            }
        }

        private bool _Directional;
        /// <summary></summary>
        public bool Directional
        {
            get
            {
                return this._Directional;
            }
            set
            {
                this._Directional = value;
            }
        }

        private int _Aggregates;
        /// <summary>
        /// number of connection between same two vertices
        /// if it's bi-directional, aggregates is 2
        /// </summary>
        public int Aggregates
        {
            get
            {
                return this._Aggregates;
            }
            set
            {
                this._Aggregates = value;
            }
        }

        private List<Link> _HiddenLinks;
        /// <summary></summary>
        public List<Link> HiddenLinks
        {
            get
            {
                return this._HiddenLinks;
            }
            set
            {
                this._HiddenLinks = value;
            }
        }

        private Range<DateTime> _LinkTimeSpan;
        /// <summary></summary>
        public Range<DateTime> LinkTimeSpan
        {
            get
            {
                return this._LinkTimeSpan;
            }
            set
            {
                this._LinkTimeSpan = value;
            }
        }
        #endregion

        #region ctor
        /// <summary>
        /// 
        /// </summary>
        public Link()
        {
            this._FromType = null;
            this._ToType = null;
            this._FromKey = string.Empty;
            this._ToKey = string.Empty;
            this._Distance = Double.PositiveInfinity;
            this._RelationType = LinkType.Citation;
            this._Directional = false;
            this._Aggregates = 1;
            this._HiddenLinks=new List<Link>();
            this._LinkTimeSpan=new Range<DateTime>(
                new DateTime(1900,1,1), 
                new DateTime(2099,12,31));
        }

        /// <summary>
        /// 
        /// </summary>
        public Link(
            Type fromMember, Type toMember, string fromKey, string toKey,
            double distance, int aggregate, LinkType linkType, bool directional)
        {
            this._FromType = fromMember;
            this._ToType = toMember;
            this._FromKey = fromKey;
            this._ToKey = toKey;
            this._Distance = distance;
            this._RelationType = linkType;
            this._Directional = directional;
            this._Aggregates = aggregate;
            this._HiddenLinks=new List<Link>();
            this._LinkTimeSpan = new Range<DateTime>(
                new DateTime(1900, 1, 1), 
                new DateTime(2099,12,31));
        }
        #endregion

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public Dictionary<string, PropertyInfo> GetBindableMembers()
        {
            Dictionary<string, PropertyInfo> props = new Dictionary<string, PropertyInfo>();
            PropertyInfo[] propArray = this.GetType().GetProperties();
            if (propArray != null && propArray.Length > 0)
            {
                foreach (PropertyInfo prop in propArray)
                {
                    if (prop.CanWrite && prop.CanRead)
                    {
                        props.Add(prop.Name, prop);
                    }
                }
            }
            return props;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="distance"></param>
        /// <returns></returns>
        public bool Filter(double distance)
        {
            if (this.Distance < distance)
                return true;
            else
                return false;
        }

        /// <summary>
        /// time series
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        public bool Filter(Range<DateTime> timeRange, int retention)
        {
            if (this._LinkTimeSpan.FromValue <= timeRange.ToValue &&
                this._LinkTimeSpan.ToValue.AddYears(retention) >= timeRange.FromValue)
                return true;
            else
                return false;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="xDoc"></param>
        /// <param name="linkNode"></param>
        public void Save(ref XmlDocument xDoc, XmlNode linkNode)
        {
            XmlDataUtil.UpdateAttribute(ref xDoc, linkNode, "FromType",this.FromType.FullName);
            XmlDataUtil.UpdateAttribute(ref xDoc, linkNode, "ToType",this.ToType.FullName);
            XmlDataUtil.UpdateAttribute(ref xDoc, linkNode,"FromKey",this.FromKey);
            XmlDataUtil.UpdateAttribute(ref xDoc, linkNode, "ToKey", this.ToKey);
            XmlDataUtil.UpdateAttribute(ref xDoc, linkNode, "Distance", this.Distance.ToString());
            XmlDataUtil.UpdateAttribute(ref xDoc, linkNode, "RelationType", this.RelationType.ToString());
            XmlDataUtil.UpdateAttribute(ref xDoc, linkNode, "Directional", this.Directional.ToString());
            XmlDataUtil.UpdateAttribute(ref xDoc, linkNode, "Aggregates", this.Aggregates.ToString());
            XmlDataUtil.UpdateAttribute(ref xDoc, linkNode, "TimeSpanFrom", this.LinkTimeSpan.FromValue.ToShortDateString());
            XmlDataUtil.UpdateAttribute(ref xDoc, linkNode, "TimeSpanTo",this.LinkTimeSpan.ToValue.ToShortDateString());

            if(this.RelationType==LinkType.CompositeLink && 
                this.HiddenLinks !=null && this.HiddenLinks.Count>0)
            {
                XmlNode hiddenLinkNodes = XmlDataUtil.AddElement(ref xDoc, linkNode, "HiddenLinks");
                foreach(Link hiddenLink in this.HiddenLinks)
                {
                    XmlNode hiddenLinkNode = XmlDataUtil.AddElement(ref xDoc, hiddenLinkNodes, "HiddenLink");
                    hiddenLink.Save(ref xDoc, hiddenLinkNode);
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public static Link Read(XmlNode linkNode)
        {
            Link link=new Link();
            Assembly assembly = typeof (EntityBase).Assembly;
            link.FromType = assembly.GetType(
                XmlDataUtil.GetAttributeValue(linkNode, "FromType", ""));
            link.ToType=assembly.GetType(
                XmlDataUtil.GetAttributeValue(linkNode,"ToType",""));
            link.FromKey = XmlDataUtil.GetAttributeValue(linkNode, "FromKey", "");
            link.ToKey = XmlDataUtil.GetAttributeValue(linkNode, "ToKey", "");
            link.Distance = double.Parse(XmlDataUtil.GetAttributeValue(linkNode, "Distance", "100"));
            link.RelationType = (LinkType) Enum.Parse(
                                               typeof (LinkType),
                                               XmlDataUtil.GetAttributeValue(
                                                   linkNode, "RelationType", LinkType.Citation.ToString()));
            link.Directional = bool.Parse(XmlDataUtil.GetAttributeValue(linkNode, "Directional", false.ToString()));
            link.Aggregates = int.Parse(XmlDataUtil.GetAttributeValue(linkNode, "Aggregates", "1"));
            DateTime fromTime =
                DateTime.Parse(
                    XmlDataUtil.GetAttributeValue(
                        linkNode, "TimeSpanFrom", (new DateTime(1900, 1, 1)).ToShortDateString()));
            DateTime toTime =
                DateTime.Parse(
                    XmlDataUtil.GetAttributeValue(
                        linkNode, "TimeSpanTo", (new DateTime(2099, 12, 31)).ToShortDateString()));
            link.LinkTimeSpan=new Range<DateTime>(fromTime, toTime);

            if(link.RelationType==LinkType.CompositeLink)
            {
                link.HiddenLinks=new List<Link>();
                XmlNodeList hiddenLinkNodes = linkNode.SelectNodes("HiddenLinks/HiddenLink");
                foreach(XmlNode hiddenLinkNode in hiddenLinkNodes)
                {
                    Link hiddenLink = Link.Read(hiddenLinkNode);
                    link.HiddenLinks.Add(hiddenLink);
                }
            }
            return link;
        }

        public static DataTable ToDataTable(List<Link> links)
        {
            DataTable dtLinks=new DataTable();
            dtLinks.Columns.Add("SrcEntityType", typeof(EntityType));
            dtLinks.Columns.Add("TgtEntityType", typeof(EntityType));
            dtLinks.Columns.Add("FromKey", typeof (string));
            dtLinks.Columns.Add("ToKey", typeof (string));
            dtLinks.Columns.Add("Distance", typeof (double));
            dtLinks.Columns.Add("LinkType", typeof (LinkType));
            dtLinks.Columns.Add("Directional", typeof (bool));
            dtLinks.Columns.Add("Composite", typeof (bool));
            dtLinks.Columns.Add("TimeSpan", typeof (string));

            foreach(Link link in links)
            {
                DataRow dr = dtLinks.NewRow();
                dr["SrcEntityType"] = EntityTypeUtil.GetEntityType(link.FromType);
                dr["TgtEntityType"] =EntityTypeUtil.GetEntityType(link.ToType);
                dr["FromKey"] = link.FromKey;
                dr["ToKey"] = link.ToKey;
                dr["Distance"] = link.Distance;
                dr["LinkType"] = link.RelationType;
                dr["Directional"] = link.Directional;
                dr["Composite"] = (link.HiddenLinks != null && link.HiddenLinks.Count > 0 &&
                                   link.RelationType == LinkType.CompositeLink);
                dr["TimeSpan"] = string.Format(
                    "{0}-{1}", link.LinkTimeSpan.FromValue.Year, link.LinkTimeSpan.ToValue.Year);
                dtLinks.Rows.Add(dr);
            }
            return dtLinks;
        }
    }
}
